#ifndef MPIH_FUNC_HPP
#define MPIH_FUNC_HPP

#include "mpiH_Comm.hpp"
#include "typeAliases.hpp"
#include <iostream>

namespace mpiFunc {
	constexpr int tag {123};
}


/* call as getMPI_Datatype<yourScalar>(); */
template<typename Scalar>
constexpr MPI_Datatype getMPI_Datatype();


template <typename T>
std::ostream& extract( std::ostream& out, T t);

template <typename T, typename ...Ts>
std::ostream& extract( std::ostream& out, T t, Ts&& ... rest );

template<typename ... Ts>
void orderedPrint( const MPI_Comm_H& comm, Ts&& ... messages );


template<typename Arr, typename T>
using ArrayTypeIfFundamental = std::enable_if_t<std::is_fundamental_v<T>, Arr>;

/* gathers single data points onto root */
template<typename Arr, typename T>
ArrayTypeIfFundamental<Arr,T> MPI_Gather_H(
	T datapoint,
	const MPI_Comm_H& comm,
	int root = 0
);

template<typename Arr>
using ArrayTypeIfNotFundamental = std::enable_if_t<
	!std::is_fundamental_v<Arr>,
	Arr>;

/* gathers equal length arrays onto root */
template<typename Arr, typename T>
ArrayTypeIfNotFundamental<Arr> MPI_Gather_H(
	const Arr& localData,
	const MPI_Comm_H& comm,
	int root = 0
);

template<typename Arr, typename T>
ArrayTypeIfFundamental<Arr,T> MPI_Allgather_H(
	T datapoint,
	const MPI_Comm_H& comm
);

template<typename Arr>
ArrayTypeIfNotFundamental<Arr> MPI_Allgather_H(
	Arr localData,
	const MPI_Comm_H& comm
);


namespace mpiDetail {

void calcDisplacements(
	Eigen::ArrayXi& displacements,
	const Eigen::ArrayXi& sizes,
	int nproc
);

} // end namespace mpiDetail


/* gathers non-equal length arrays onto root */
template<typename T>
T MPI_Gatherv_H(
	const T& localArray,
	const MPI_Comm_H& comm,
	int root = 0
);


template<typename T>
T MPI_Allgatherv_H(
	const T& localArray,
	const MPI_Comm_H& comm
);

template<typename T>
std::enable_if_t<std::is_fundamental_v<T>,void> MPI_Bcast_H(
	T&,
	const MPI_Comm_H&,
	int root = 0
);

template<typename T>
std::enable_if_t<!std::is_fundamental_v<T>,void> MPI_Bcast_H(
	T&,
	const MPI_Comm_H&,
	int root = 0
);

template<typename Derived>
void MPI_Bcast_H(
	Eigen::DenseBase<Derived>&,
	const MPI_Comm_H&,
	int root = 0,
	bool resize = true
);

template<typename T>
std::enable_if_t<std::is_fundamental_v<T>, void> MPI_Send_H(
	const T&, const MPI_Comm_H&, int to = 0, int tag = mpiFunc::tag
);

template<typename T>
std::enable_if_t<!std::is_fundamental_v<T>, void> MPI_Send_H(
	const T&, const MPI_Comm_H&, int to = 0, int tag = mpiFunc::tag
);

template<typename T>
std::enable_if_t<std::is_fundamental_v<T>, T> MPI_Recv_H(
	const MPI_Comm_H&, int from = MPI_ANY_SOURCE,
	int tag = MPI_ANY_TAG, MPI_Status* status = MPI_STATUS_IGNORE
);

template<typename T>
std::enable_if_t<!std::is_fundamental_v<T>, T> MPI_Recv_H(
	const MPI_Comm_H&, int from = MPI_ANY_SOURCE,
	int tag = MPI_ANY_TAG, MPI_Status* status = MPI_STATUS_IGNORE
);

MPI_Info MPI_Info_no_locks();



#include "mpiH_Func.tpp"

#endif